import Crc from '../ble_sdk/crc'
import {
	BleManager
} from '../ble_sdk/index'
import {
	formatNum,
	dec2hex,
	hex2dec,
	hex2ascll,
	parseShort
} from '../util'
// import { wavelet_filter_convert } from '../ble_sdk/waveFiltering'
// import * as echarts from '../../ec-canvas/echarts'

const fs = wx.getFileSystemManager()

module.exports = class BP2Protocol extends BleManager {
	streamNo = 0 // 指令流水号
	cmdRecord = '' // 记录上位机给设备端发送的指令，用来和接收的数据包做对应
	reversedCmdRecord = '' // 同上
	datas = '' // 收到的设备端数据包全量
	oldCallbackData = '' // 上一次的数据包，用来判断是否是重复包
	deviceInfo = null // 存设备信息

	fileList = [] // 读到的设备文件列表
	fileData = [] // 当前读取文件的所有内容
	fileOffset = 0 // 当前读取文件的内容偏移量
	fileSize = 0 // 当前读取文件的大小
	downloadPercent = 0 // 当前读取文件的进度
	isReading = false // 当前是否正在读文件
	curFile = '' // 当前读取文件的内容
	oneFileReadDone = false // 一个文件读完
	fileDownloaded = 0 // 当前下载完成的文件数
	readFileTimer = null // 读文件的定时器

	heartBeatSoundOn = false // 当前设备的心跳音状态
	devicePercent = 0 // 电量
	deviceRunInfo = {} // 当前设备的运行状态
	deviceRunStatus = 0 //设备运行状态
	allWaveData = [] // 本次测量的所有波形数据
	waveDataArray = [] // 当前的波形
	reset = 1 // ？？
	BpCmdTimer = null // 实时血压指令的timer，500ms
	EcgCmdTimer = null // 发送实时心电指令的timer，500ms
	drawEcgTimer = null // 绘制心电图的timer，50ms
	isTesting = false // 当前是否正在测量

	constructor(Device) {
		super(Device)
	}

	// 生成指令，入参都是16进制string
	generateCmd(cmd, type, data) {
		this.cmdRecord = cmd.toLowerCase()
		const reversedCmd = formatNum((255 - parseInt(cmd, 16)).toString(16))
		this.reversedCmdRecord = reversedCmd
		const len = dec2hex(data.length / 2) // 指令data参数字节数，所以/2
		const No = formatNum(this.streamNo.toString(16))
		this.streamNo = (this.streamNo + 1) % 255
		const resTemp = 'a5' + cmd + reversedCmd + type + No + len + data
		const crc = Crc.calculateCrc(resTemp)
		return (resTemp + crc).toLowerCase()
	}

	// 解析接受到的设备端数据包
	parseData(res) {
		//不要存储不是以a5开头脏的数据
		if (this.datas == '' && !res.startsWith('a5')) {
			return;
		}
		this.datas += res
		let start = this.datas.indexOf('a5') // a5标志一次完整数据包的开始，正确情况下start都是0
		if (start != -1) { // 说明有一个数据包来了
			let len = (hex2dec(this.datas.substr(start + 10, 4))) * 2 + 16
			if (len <= this.datas.length) { // 一个数据包已经完整了
				let cmd = this.datas.substr(start + 2, 2)
				let cmdReverse = this.datas.substr(start + 4, 2)
				// 判断当前数据包是不是刚才发送的指令的回应包
				if (cmd === this.cmdRecord && cmdReverse === this.reversedCmdRecord) {
					let data = this.datas.substr(start, len) // 截取完整数据包
					if (Crc.checkCrc(data)) { // crc通过
						const content = data.substr(14, data.length - 16)
						const type = data.substr(6, 2)
						const no = data.substr(8, 2)
						const crc = data.substr(data.length - 2, 2)
						const pkg = {
							data,
							cmd,
							cmdReverse,
							type,
							no,
							content,
							crc
						}
						// console.log(pkg)
						// 到这里对一个ark包的基本处理完成，后面就是根据指令不同，具体怎么处理content
						this.pkgHandler(pkg)
					}
				}
				// 最后把处理完的上一个数据包截掉
				this.datas = this.datas.substr(len)
				if (this.datas != '' && !this.datas.startsWith('a5')) {
					this.datas = '';
				}
			}
		}
	}

	// parseData之后处理content
	pkgHandler(pkg) {
		switch (pkg.cmd.toLowerCase()) {
			case 'e1': // 获取设备信息
				this.getDeviceInfo(pkg.content)
				break
			case 'f1': // 获取文件列别表
				this.getFileList(pkg.content)
				break
			case 'f2': // 开始读取文件内容
				this.fileReadStart(pkg.content)
				break
			case 'f3': // 读文件内容
				this.fileRead(pkg.content)
				break
			case 'f4': // 读文件结束
				this.fileReadEnd()
				break
			case 'f9': // 获取用户列表
				this.getUserList(pkg)
				break
			case '00': // 获取配置参数
				this.getDeviceConfig(pkg.content)
				break
			case '01': // 校零
				this.calibrationZero(pkg.content)
				break
			case '02': // 校准
				this.calibrationSlope(pkg.content)
				break
			case '05': // 获取实时压
				this.getRealTimePresure(pkg.content)
				break
			case '06': // 获取实时运行状态
				this.getRunStatus(pkg.content)
				break
			case '07': // 获取实时波形
				this.getRealTimeWave(pkg.content)
				break
			case '08': // BP2获取实时数据
				this.RealTimeData(pkg.content)
				break
			case 'e3': // 恢复出厂设置
				if (obj.type === '01') {
					wx.showToast({
						title: '恢复出厂设置完成',
						duration: 1000
					})
				} else {
					wx.showToast({
						title: '恢复出厂设置异常',
						duration: 1000
					})
				}
				break
		}
	}

	// 获取设备信息，包括sn，设备类型等
	getDeviceInfo(content) {
		// 设备类型为第21-22字节，sn长度为第38字节，sn为第39-56字节
		if (content) {
			const deviceType = content.substr(40, 4)
			const snLength = parseInt(content.substr(74, 2), 16)
			const deviceSn = hex2ascll(content.substr(76, 36)).slice(0, snLength) // 尾巴会有多出来的东西
			console.log('sn:', deviceSn)
			this.deviceInfo = {
				deviceType,
				deviceSn
			}
		}
	}

	// 获取文件列表
	getFileList(content) {
		if (content) {
			const fileNum = parseInt(content.substr(0, 2), 16)
			console.log('设备内总文件数：', fileNum)
			const data = content.substr(2, content.length)
			let value = '',
				hmsTime = '',
				ymdTime = ''
			let list = []
			for (let j = 0; j < data.length; j += 32) { // 后面每16个字节就是一个文件名
				value = data.substr(j, 32)
				// console.log(value)
				let fileName = hex2ascll(value)
				// console.log(fileName)
				hmsTime = fileName.substr(9, 2) + ':' + fileName.substr(11, 2) + ':' + fileName.substr(13, 2)
				ymdTime = fileName.substr(1, 4) + '-' + fileName.substr(5, 2) + '-' + fileName.substr(7, 2)
				list.push({
					value, // 当前文件的16进制完整数据保留
					hmsTime,
					ymdTime
				})
			}
			// 这里过滤掉非52是因为0x52是R，R开头的文件是展示需要的文件数据结构，而61不要是因为0x61是a，a开头的文件表示分析结果文件，两类文件是一一对应的，一次测量结果存两类文件
			// 而用value作为oldName是为了在后续指定下载文件时作为传递的文件名参数，由于已经是16进制unicode，就不用转化了
			this.fileList = list.filter((item) => {
				return item.value.substr(0, 2) === '52'
			})
			wx.showToast({
				title: '获取成功',
				duration: 1000
			})
		} else {
			wx.showToast({
				title: '未读取到文件',
				duration: 1000
			})
		}
	}

	// 开始读文件
	fileReadStart(content) {
		this.fileOffset = 0 // 开始读一个文件,偏移量先置零
		if (content) {
			this.fileSize = hex2dec(content)
			console.log('当前文件大小：', this.fileSize)
			const cmd = this.generateCmd('f3', '00', '00000000')
			this.writeValue(cmd)
		}
	}

	// 读文件内容
	fileRead(content) {
		this.fileOffset += content.length / 2
		console.log('当前读取进度：', this.fileOffset)
		// 先拿到这个文件的所有数据，push到fileData里再解析，没拿完就继续F3指令读，但是偏移量改变
		if (this.fileOffset < this.fileSize) {
			this.downloadPercent = this.fileOffset * 100 / this.fileSize
			this.fileData.push(content)
			const cmdData = dec2hex(this.fileOffset, 8)
			const cmd = this.generateCmd('f3', '00', cmdData)
			this.writeValue(cmd)
		} else {
			// 读一个文件结束,存结果并还原过渡变量
			this.fileData.push(content)
			this.fileDownloaded += 1
			this.downloadPercent = 100
			this.oneFileReadDone = true
			this.curFile = this.parseFile(this.fileData.join('')) // 处理ecg的文件内容
			console.log(this.curFile)

			// 读文件结束指令
			const cmd = this.generateCmd('f4', '00', '')
			this.writeValue(cmd)
		}
	}

	// 读文件结束
	fileReadEnd() {
		// 读一个文件结束,存结果并还原过渡变量
		this.fileData = []
		this.fileDownloaded = 0
		this.downloadPercent = 0
		this.isReading = false
	}

	// 解析文件内容
	parseFile(fileDataStr) {
		const timeStr = fileDataStr.substr(fileDataStr.length - 40, 8) // 最后20个字节为文件尾部，其中前4个字节为recording_time，
		const recordingTime = hex2dec(timeStr)
		const content = fileDataStr.substr(20, fileDataStr.length - 60) // 开始的10个字节为文件头，记录文件版本，中间的就是心电数据，掐头去尾共去掉30字节
		const ecgData = this.parseEcgData(content)
		return {
			ecgData,
			recordingTime
		}
	}
	// 解析文件中的心电数据部分
	parseEcgData(contentInput) {
		let contentOutput = []
		let unComNum = 0
		let lastComData = 0
		let ecgData = 0
		const comMaxVal = 127 // 压缩最大值
		const comMinVal = -127 // 压缩最小值
		const comRetOriginal = -128 // 需要保存原始值返回值
		const comRetPostTive = 127 // 需要保存扩展数为正数返回值
		const comRetNegative = -127 // 需要保存扩展数为负数返回值
		const uncomRetInvali = -32768 // 解压无需处理返回值
		for (let i = 0; i < contentInput.length; i += 2) { // 一个字节一个字节地处理
			let ecgCompressData = parseInt(contentInput.substr(i, 2), 16)
			if (ecgCompressData > 127) // 有符号数
				ecgCompressData -= 256
			switch (unComNum) {
				case 0:
					if (ecgCompressData === comRetOriginal) {
						unComNum = 1
						ecgData = uncomRetInvali
					} else if (ecgCompressData === comRetPostTive) { // 正
						unComNum = 3
						ecgData = uncomRetInvali
					} else if (ecgCompressData === comRetNegative) { // 负
						unComNum = 4
						ecgData = uncomRetInvali
					} else {
						ecgData = lastComData + ecgCompressData
						lastComData = ecgData
					}
					break
				case 1: // 原始数据字节低位
					lastComData = ecgCompressData
					unComNum = 2
					ecgData = uncomRetInvali
					break
				case 2: // 原始数据字节高位
					if (ecgCompressData < 0) {
						ecgCompressData = 256 + ecgCompressData
					}
					ecgData = (lastComData & 0xFF) | (ecgCompressData * 256)
					if (ecgData > 32767) {
						ecgData -= 65536
					}
					lastComData = ecgData
					unComNum = 0
					break
				case 3:
					if (ecgCompressData < 0) {
						ecgCompressData = 256 + ecgCompressData
					}
					ecgData = comMaxVal + (lastComData + ecgCompressData)
					lastComData = ecgData
					unComNum = 0
					break
				case 4:
					if (ecgCompressData < 0) {
						ecgCompressData = 256 + ecgCompressData
					}
					ecgData = comMinVal + (lastComData - ecgCompressData)
					lastComData = ecgData
					unComNum = 0
					break
				default:
					break
			}
			if (ecgData > 32767) { // 带符号位，转负数
				ecgData -= 65536
			}
			if (ecgData !== uncomRetInvali) {
				ecgData = ecgData * 0.002467
				contentOutput.push(ecgData)
			}
		}
		return contentOutput
	}

	// ecg数据写到缓存， ecgData为把16进制原文件格式解析后的点数据list
	writeEcgData2Txt(ecgData) {
		return new Promise((resolve, reject) => {
			// 先写文件内容到缓存里
			const txtFileName = '/' + Math.floor(Math.random() * 100000) + '.txt'
			const tempFilePath = wx.env.USER_DATA_PATH + txtFileName
			fs.writeFile({
				filePath: tempFilePath,
				data: 'F-0-01,125,II,405,' + ecgData.join(','),
				encoding: 'utf8',
				success: () => {
					// 写完之后不用saveFile也能调分析接口，ios调saveFile会报错，目前没找到解决方法
					wx.showModal({
						title: '已写入缓存文件',
						content: '路径：' + tempFilePath,
						showCancel: false
					})
					resolve(tempFilePath)
					// 现在文件在缓存里，可以调分析了，和saveFile的区别就在于saveFile的文件在本地了
				},
				fail: (err) => {
					console.log(err)
					wx.showToast({
						title: '写文件失败',
						icon: 'error',
						duration: 1000
					})
					reject(err)
				}
			})
		})
	}

	// 获取用户列表
	getUserList() {
		console.log('getUserList')
	}

	// 获取配置信息
	getDeviceConfig(content) {
		const prev_calib_zero = hex2dec(content.substr(0, 8)) // int型，4字节，上一次校零adc值
		const last_calib_zero = hex2dec(content.substr(8, 8)) // int型，4字节，最后一次校零adc值
		const calib_slope = hex2dec(content.substr(16, 8)) // int型，4字节，校准斜率值 * 100
		const slope_pressure = hex2dec(content.substr(24, 4)) // short型，2字节，校准斜率时用的压力值
		const calib_ticks = hex2dec(content.substr(28, 8)) // long型，4字节，最后一次校准时间
		const sleep_ticks = hex2dec(content.substr(36, 8)) // long型，4字节，上次进休眠待机时间
	}

	// 校零
	calibrationZero(content) {
		const calib_zero = hex2dec(content.substr(0, 8)) // int型，4字节，校零adc值
	}

	// 校准
	calibrationSlope(content) {
		console.log(content)
	}

	// 获取实时压
	getRealTimePresure(content) {
		const pressure = hex2dec(content.substr(0, 4)) // short型，2字节，实时压（mmHg）*100
	}

	// 获取实时运行状态
	getRunStatus(content) {
		// 这里是主机状态不是运行状态
		const status = parseInt(content.substr(0, 2),
			16) // 主机状态,枚举类型，其中：0关机，1数据回顾，2充电，3开机预备，4血压测量中，5血压测量结束，6心电测量中，7心电测量结束
		const batteryInfo = content.substr(2, 8) // 电池状态结构体
		const batteryState = parseInt(batteryInfo.substr(0, 2), 16) // 电池状态，0正常使用，1充电中，2充满，3低电量
		const batteryPercent = parseInt(batteryInfo.substr(2, 2), 16) // 电量百分比
		const batteryVoltage = hex2dec(batteryInfo.substr(4, 4)) // 电池电压mv
		this.devicePercent = batteryPercent
		console.log('status', status);
		this.deviceRunStatus = status;
	}

	// 07 获取实时波形
	getRealTimeWave(content) {
		const type = parseInt(content.substr(0, 2), 16) // 测量类型，0（血压测量中）,1（血压测量结束），2（心电测量中），3（心电测量结束）
		// 根据type不同，后面的data结构也不同，但是固定20字节
		const data = content.substr(2, 40)
		const res = {}
		// bp2的runStatus复杂些, 也根据type不同而不同，这里是自定义，0表示血压中，1表示血压结束，2表示心电中，3表示心电结束，4表示信号弱，5表示导联脱落
		switch (type) {
			case 0: // 血压测量中
				res.is_deflating = parseInt(data.substr(0, 2), 16) // 是否放气 0：否；1：是
				res.pressure = parseShort(parseInt(data.substr(4, 2) + data.substr(2, 2), 16)) / 100 // 实时压, 要用拿到的值 / 100
				res.is_get_pulse = parseInt(data.substr(6, 2), 16) // 是否检测到脉搏波 0：否；1：是
				res.pulse_rate = hex2dec(data.substr(8, 4)) // 脉率
				this.deviceRunInfo = {
					hr: res.pulse_rate,
					runStatus: type,
					pressure: res.pressure
				}
				break;
			case 1: // 血压测量结束
				res.is_deflating = parseInt(data.substr(0, 2), 16) // 是否放气 0：否；1：是
				res.pressure = parseShort(parseInt(data.substr(4, 2) + data.substr(2, 2), 16)) / 100 // 实时压, 要用拿到的值 / 100
				res.systolic_pressure = hex2dec(data.substr(6, 4)) // 收缩压
				res.diastolic_pressure = hex2dec(data.substr(10, 4)) // 舒张压
				res.mean_pressure = hex2dec(data.substr(14, 4)) // 平均圧
				res.pulse_rate = hex2dec(data.substr(18, 4)) // 脉率
				res.state_code = parseInt(data.substr(22, 2), 16) // 状态码
				res.medical_result = parseInt(data.substr(24, 2), 16) // 诊断结果 bit0:心率不齐
				this.deviceRunInfo = {
					hr: res.pulse_rate,
					runStatus: type,
					pressure: res.pressure,
					systolic_pressure: res.systolic_pressure,
					diastolic_pressure: res.diastolic_pressure,
					mean_pressure: res.mean_pressure
				}
				break
			case 2: // 心电测量中
				res.duration = hex2dec(data.substr(0, 8)) // 当前测量时长
				res.special_status = hex2dec(data.substr(8, 8)) // 特殊状态 bit0:是否信号弱，bit1:是否导联脱落
				res.pulse_rate = hex2dec(data.substr(16, 4)) // 脉率
				this.deviceRunInfo = {
					recordTime: res.duration,
					hr: res.pulse_rate,
					runStatus: res.special_status === 1 ? 4 : (res.special_status === 3 ? 5 : type)
				}
				break
			case 3: // 心电测量结束
				res.result = hex2dec(data.substr(0, 8)) // 诊断结果
				res.hr = hex2dec(data.substr(8, 4)) // 心率
				this.deviceRunInfo = {
					hr: res.hr,
					runStatus: type
				}
				break
		}
		// data后面才是waveform
		const waveform = content.substr(42) // 2 的 type 和 40 的data
		this.handleWavefrom(waveform)
	}

	// 08 实时数据， 08指令和07指令的不同在于08多了一个runstatus
	RealTimeData(content) {
		// 前面9个字节是RunStatus
		this.getRunStatus(content.substr(0, 18))
		// 后面的数据包就和07指令一样了
		this.getRealTimeWave(content.substr(18))
	}

	// 处理波形数据
	handleWavefrom(waveform) {
		const samplingRate = hex2dec(waveform.substr(0, 4)) // 采样率
		let waveData = waveform.substr(4)
		this.isTesting = this.deviceRunInfo.runStatus == 2
		for (let i = 0; i < waveData.length; i += 4) {
			let wave = parseShort(hex2dec(waveData.substr(i, 4)))
			if (this.isTesting) {
				this.allWaveData.push(wave) // 这里存原数据缓存，是方便写文件或者调AI分析
			}
			wave = wave * (1.0035 * 1800) / (4096 * 178.74) // 这里就是乘了协议里的0.002467，也就是除以405
			// 滤波处理
			// let data = waveFilter(wavelet_filter_convert(wave, this.reset) * 2)
			// this.waveDataArray.push(data)

			this.waveDataArray.push(wave)
			this.reset = 0
		}
	}

	// 绘制ecg波形图
	drawEcg(canvas, width, height, dpr) {
		const chart = echarts.init(canvas, null, {
			width: width,
			height: height,
			devicePixelRatio: dpr
		})
		canvas.setChart(chart)

		const option = {
			grid: {
				top: '0',
				left: '0',
				right: '0',
				bottom: '0'
			},
			xAxis: {
				type: 'category',
				data: [],
				show: false
			},
			yAxis: {
				x: 'center',
				type: 'value',
				show: false,
				min: -3,
				max: 3
			},
			series: [{
				symbolSize: 0, //拐点大小
				type: 'line',
				smooth: true,
				data: [],
				color: ['#ff2727'],
				itemStyle: {
					normal: {
						lineStyle: {
							width: 2,
							type: 'solid'
						}
					}
				}
			}]
		}
		// 初始化一条线
		for (let i = 0; i < 500; i++) { // x轴总共500个点, 也就是一页是4秒的数据
			option.xAxis.data.push(i)
			option.series[0].data.push(0)
		}
		let count = 0
		this.drawEcgTimer = setInterval(() => {
			// waveDataArray是500ms一次的包，而心电图50ms绘制一次，所以一个包要绘制10次
			if (this.isTesting) {
				const oneTimeLen = Math.floor(this.waveDataArray.length / 10)
				const nowWave = this.waveDataArray.splice(0, oneTimeLen)
				// 把option当前位置的数据替换为当前绘制的波形数据
				if (count + oneTimeLen <= 500) {
					option.series[0].data.splice(count, oneTimeLen, ...nowWave)
				} else {
					const len1 = (count + oneTimeLen) % 500
					const len2 = oneTimeLen - len1
					option.series[0].data.splice(count, len2, ...nowWave.slice(0, len2))
					option.series[0].data.splice(0, len1, ...nowWave.slice(len2))
				}
				count = (count + oneTimeLen) % 500
				// 绘制刷新出的3个空白点 
				for (let i = 0; i < 3; i++) {
					option.series[0].data[(count + i + 1) % 500] = ''
				}
			} else { // 没有测量就是个简单的动图
				for (let i = 0; i < 3; i++) {
					option.series[0].data[(count + i + 1) % 500] = '' //刷新处的空白
					option.series[0].data[count] = 0
					count = ++count % 500
				}
			}
			chart.setOption(option, true)
		}, 50)
	}
}

function waveFilter(data) {
	// 这里只简单设置阈值[-3, 3]
	return Math.abs(data) > 3 ? (data / Math.abs(data) * 3) : data
}